using System;
using System.Collections.Generic;
using CI.DAL;
using CI.Interfaces;
using CI.Organism;

namespace CI.Mesopotamia.Dal
{
    /// <summary>
    /// Test DAL, just hard coded at the moment while expermimenting, want to see the relationship between 
    /// genes and cells and how we are going to store them
    /// </summary>
    public class TestDAL : IDAL
    {
        private readonly SimEnv _simEnv = SimEnv.Instance;

        /// <summary>
        /// Load the organisms out of the database
        /// </summary>
        /// <param name="environment">Environment name to load</param>
        /// <param name="hostEnvironment">Host environment</param>
        /// <param name="maxSaveCount">Ignored</param>
        /// <param name="selectedOrganismsToLoad"></param>
        /// <returns>Organisms</returns>
        public IHostingEnvironment LoadHostingEnvironment(string environment, IHostingEnvironment hostEnvironment, int maxSaveCount, List<Guid> selectedOrganismsToLoad)
        {
            _simEnv.TraceLogger.LogDebug("Loading Organisms - Start");

            // save the host environment to this
            _simEnv.HostEnv = hostEnvironment;

            // create amoebas
            if (environment == "Survival of the Fattest")
            {
                CreateAmoeba(-1.5f, 1.5f, hostEnvironment);
                CreateAmoeba(-1.5f, -1.5f, hostEnvironment);
                CreateAmoeba(1.5f, 1.5f, hostEnvironment);
                CreateAmoeba(1.5f, -1.5f, hostEnvironment);
            }

            bool createNNet = !environment.ToLower().Contains("genesis");

            // create iRobots
            CreateiRobot(0.5f, 0, hostEnvironment, createNNet);
            CreateiRobot(-0.5f, 0, hostEnvironment, createNNet);

            _simEnv.TraceLogger.LogDebug("Loading Organisms - End");

            return hostEnvironment;
        }

        /// <summary>
        /// create amoeba
        /// </summary>
        /// <param name="xAxis"></param>
        /// <param name="zAxis"></param>
        /// <param name="hostEnvironment"></param>
        public void CreateAmoeba(float xAxis, float zAxis, IHostingEnvironment hostEnvironment)
        {
            IDNA _dna;
            IOrganism _organism;
            IChromosomeCollection _chromosomes;
            IChromosome _chromosomeCellular;
            IChromosome _chromosomeReproduction;
            IGeneCollection _genesCellular;
            IGeneCollection _genesReproductive;
            GeneCellular _geneCreate;
            GeneCellular _geneDestory;
            GeneReproductionAsexual _geneASexual;
            IRequirementCollection _requirementsCreate;
            IRequirementCollection _requirementsDestroy;
            IRequirementCollection _requirementsASexual;
            IRequirement _requirementBodyCreate;
            IRequirement _requirementBodyDestory;
            IPhysical _body;
            ICellCollection _cells;
            Int64 _AmoebaEnergy = 25;
            float _AmoebaMutationRate = 0.001f;

            _organism = new CI.Organism.Organism(hostEnvironment, Guid.NewGuid(), _AmoebaEnergy, 0);
            _geneCreate = new GeneCellular(_organism, Guid.NewGuid());
            _geneDestory = new GeneCellular(_organism, Guid.NewGuid());
            _geneASexual = new GeneReproductionAsexual(_organism, Guid.NewGuid());
            _cells = new CellCollection();
            _dna = new DNA();
            _chromosomes = new ChromosomeCollection();
            _chromosomeCellular = new Chromosome(Guid.NewGuid());
            _chromosomeReproduction = new Chromosome(Guid.NewGuid());
            _genesCellular = new GeneCollection();
            _genesReproductive = new GeneCollection();
            _requirementsCreate = new RequirementCollection();
            _requirementsDestroy = new RequirementCollection();
            _requirementsASexual = new RequirementCollection();
            _organism.Organisms = hostEnvironment.Organisms;

            // set it back in z direction
            _organism.Location.ZAxis = zAxis; //_organism.Location.ZAxis + 1.5f;
            _organism.Location.XAxis = xAxis; //_organism.Location.XAxis - 1.5f;
            _organism.Location.XRadius = 0.1f;
            _organism.Location.YRadius = 0.1f;
            _organism.Location.ZRadius = 0.1f;

            // add the cell collection
            _organism.Cells = _cells;
            // create dna structure
            _organism.DNA = _dna;
            // setup the chromosome collection
            _organism.DNA.Chromosomes = _chromosomes;
            // add the cellular chromosome
            _chromosomes.Add(_chromosomeCellular);
            // add the reproduction chrosomes
            _chromosomes.Add(_chromosomeReproduction);
            // add a gene collection
            _chromosomeCellular.Genes = _genesCellular;
            // add the reproductive genes to the repoductive chromosome
            _chromosomeReproduction.Genes = _genesReproductive;

            // create the pysical body
            _body = new Amoeba(_organism, Guid.NewGuid());
            _requirementBodyCreate = AddGeneToCreateOrganism(_organism, _genesCellular, _geneCreate, _requirementsCreate, _body);
            _requirementBodyDestory = AddGeneToDestroyOrganism(_organism, _genesCellular, _geneDestory, _requirementsDestroy, _body, 1000);
            CreateAmoebaAsexualReproduction(_organism, _genesReproductive, _geneASexual, _requirementsASexual, _body, _AmoebaMutationRate);

            // as we want the Organism to grow from dna the nerual system will be empty
            _organism.NeuralSystem = null;

            // set alive organisms
            hostEnvironment.Organisms.AliveAdd(_organism);

        }

        private static void CreateAmoebaAsexualReproduction(IOrganism organism, IGeneCollection genesReproductive, GeneReproductionAsexual geneASexual, IRequirementCollection requirementsASexual, IPhysical body, float amoebaMutationRate)
        {
            // add a ASexual gene
            genesReproductive.Add(geneASexual);
            // tell the gene who the Organism is
            geneASexual.Organism = organism;
            // set the mutation rate
            geneASexual.MutationRate = amoebaMutationRate;
            // set the requirements list
            geneASexual.Requirements = requirementsASexual;
            // add the requirement to start reproducing
            requirementsASexual.Add(new HBOffsetTimeCell(organism, 200, body, ComparisonType.GreaterThan, Guid.NewGuid()));
            requirementsASexual.Add(new HBOffsetTimeCell(organism, 203, body, ComparisonType.LessThan, Guid.NewGuid()));
        }

        private static IRequirement AddGeneToDestroyOrganism(IOrganism organism, IGeneCollection genesCellular, GeneCellular geneDestory, IRequirementCollection requirementsDestroy, IPhysical body, uint hbOffset)
        {
            // add a gene to create the Organism
            genesCellular.Add(geneDestory);
            // tell the gene who the Organism is
            geneDestory.Organism = organism;
            // add a requirement colleciton to that gene
            geneDestory.Requirements = requirementsDestroy;
            // destroy the body after the second heart beat
            IRequirement requirementBodyDestory = new HBOffsetTimeCell(organism, hbOffset, body, ComparisonType.Equal, Guid.NewGuid());
            // add a body as this is the first step
            requirementsDestroy.Add(requirementBodyDestory);
            // set the action type as adding
            geneDestory.ActionType = ActionType.DelCell;
            // set the gene's cell
            geneDestory.Cell = body;
            return requirementBodyDestory;
        }

        /// <summary>
        /// create iRobot
        /// </summary>
        /// <param name="xAxis"></param>
        /// <param name="zAxis"></param>
        /// <param name="hostEnvironment"></param>
        /// <param name="createNNet"></param>
        public void CreateiRobot(float xAxis, float zAxis, IHostingEnvironment hostEnvironment, bool createNNet)
        {
            IDNA _dna;
            IOrganism _organism;
            IChromosomeCollection _chromosomes;
            IChromosome _chromosomeCellular;
            IChromosome _chromosomeReproduction;
            IGeneCollection _genesCellular;
            IGeneCollection _genesReproductive;
            GeneCellular _geneCreate;
            GeneCellular _geneDestory;
            GeneCellular _geneNeuralForward;
            GeneCellular _geneNeuralTurnRight;
            GeneCellular _geneNeuralTurnLeft;
            GeneCellular _geneNeuralEnergy;
            GeneCellular _geneCyclops;
            GeneReproductionAsexual _geneASexual;
            IRequirementCollection _requirementsCreate;
            IRequirementCollection _requirementsDestroy;
            IRequirementCollection _requirementsASexual;
            IRequirementCollection _requirementsNeuralForward;
            IRequirementCollection _requirementsNeuralTurnRight;
            IRequirementCollection _requirementsNeuralTurnLeft;
            IRequirementCollection _requirementsNeuralEnergy;
            IRequirementCollection _requirementsCyclops;
            IRequirement _requirementBodyCreate;
            IRequirement _requirementBodyDestory;
            IPhysical _body;
            INeuron _ntl;
            INeuron _ntr;
            INeuron _nf;
            INeuron _nEnergy;
            INeuron _nCyclops;
            ISynapse _synapseEnergy;
            ISynapse _synapseInputEnergyFoward;
            ISynapse _synapseInputCyclopsVetoForward;
            ISynapse _synapseInputFrontLeftBumperVetoForward;
            ISynapse _synapseInputFrontRightBumperVetoFoward;
            ISynapse _synapseInputCyclops;
            ISynapse _synapseOutputCyclops;
            ISynapse _synapseInputTurnLeft;
            ISynapse _synapseInputTurnRight;
            ISynapse _synapseOutputTurnLeft;
            ISynapse _synapseOutputTurnRight;
            ISynapse _synapseOutputForward;
            ICellCollection _cells;
            Int64 _irobotEnergy = 1000;
            Int64 _irobotReproductiveEnergy = 2000;
            float _irobotMutationRate = 0.05f;

            _organism = new Organism.Organism(hostEnvironment, Guid.NewGuid(), _irobotEnergy, 0);
            _geneCreate = new GeneCellular(_organism, Guid.NewGuid());
            _geneDestory = new GeneCellular(_organism, Guid.NewGuid());
            _geneASexual = new GeneReproductionAsexual(_organism, Guid.NewGuid());
            _geneNeuralForward = new GeneCellular(_organism, Guid.NewGuid());
            _geneNeuralTurnRight = new GeneCellular(_organism, Guid.NewGuid());
            _geneNeuralTurnLeft = new GeneCellular(_organism, Guid.NewGuid());
            _geneNeuralEnergy = new GeneCellular(_organism, Guid.NewGuid());
            _geneCyclops = new GeneCellular(_organism, Guid.NewGuid());
            _cells = new CellCollection();
            _dna = new DNA();
            _chromosomes = new ChromosomeCollection();
            _chromosomeCellular = new Chromosome(Guid.NewGuid());
            _chromosomeReproduction = new Chromosome(Guid.NewGuid());
            _genesCellular = new GeneCollection();
            _genesReproductive = new GeneCollection();
            _requirementsCreate = new RequirementCollection();
            _requirementsDestroy = new RequirementCollection();
            _requirementsASexual = new RequirementCollection();
            _requirementsNeuralForward = new RequirementCollection();
            _requirementsNeuralTurnRight = new RequirementCollection();
            _requirementsNeuralTurnLeft = new RequirementCollection();
            _requirementsNeuralEnergy = new RequirementCollection();
            _requirementsCyclops = new RequirementCollection();
            _organism.Organisms = hostEnvironment.Organisms;

            // set it back in z direction
            _organism.Location.ZAxis = zAxis;
            _organism.Location.XAxis = xAxis;
            _organism.Location.XRadius = 0.2f;
            _organism.Location.YRadius = 0.1f;
            _organism.Location.ZRadius = 0.2f;

            // set the CIET
            _organism.CIET = 50;
            // save the init ciet
            _organism.InitCIET = _organism.CIET;
            // add the cell collection
            _organism.Cells = _cells;
            // create dna structure
            _organism.DNA = _dna;
            // setup the chromosome collection
            _organism.DNA.Chromosomes = _chromosomes;
            // add the cellular chromosome
            _chromosomes.Add(_chromosomeCellular);
            // add the reproduction chrosomes
            _chromosomes.Add(_chromosomeReproduction);
            // add a gene collection
            _chromosomeCellular.Genes = _genesCellular;
            // add the reproductive genes to the repoductive chromosome
            _chromosomeReproduction.Genes = _genesReproductive;

            // create the pysical body
            _body = new iRobot(_organism, Guid.NewGuid());
            _requirementBodyCreate = AddGeneToCreateOrganism(_organism, _genesCellular, _geneCreate, _requirementsCreate, _body);
            _requirementBodyDestory = AddGeneToDestroyOrganism(_organism, _genesCellular, _geneDestory, _requirementsDestroy, _body, 10000);
            CreateiRobotAsexualReproduction(_organism, _genesReproductive, _geneASexual, _requirementsASexual, _body, _irobotReproductiveEnergy, _irobotMutationRate);

            // create the simple neural network system
            _organism.NeuralSystem = new SimpleNeuralSystem(_organism);

            // just an empty energy one to check energy
            CreateGeneWithHBOffsetOf1FromParent(_genesCellular, _geneNeuralEnergy, _geneCreate, _requirementsNeuralEnergy, _organism);
            _nEnergy = new StepNeuron(100, _organism.NeuralSystem, Guid.NewGuid());
            _synapseEnergy = new SimpleSynapse(Guid.NewGuid(), _organism.NeuralSystem);
            _synapseEnergy.AddSynapticConnectionSource(_body.GetSynapseIO(Globals.ENERGY));
            _synapseEnergy.AddSynapticConnectionTarget(_nEnergy);
            _nEnergy.InputSynapse.Add(_synapseEnergy);
            _geneNeuralEnergy.Cell = _nEnergy;

            if (createNNet)
            {
                #region Add Neural Network
                CreateGeneWithHBOffsetOf1FromParent(_genesCellular, _geneNeuralForward, _geneCreate, _requirementsNeuralForward, _organism);
                CreateGeneWithHBOffsetOf1FromParent(_genesCellular, _geneNeuralTurnLeft, _geneCreate, _requirementsNeuralTurnLeft, _organism);
                CreateGeneWithHBOffsetOf1FromParent(_genesCellular, _geneNeuralTurnRight, _geneCreate, _requirementsNeuralTurnRight, _organism);
                CreateGeneWithHBOffsetOf1FromParent(_genesCellular, _geneCyclops, _geneCreate, _requirementsCyclops, _organism);

                // create the neuron with a activation value of x
                _nf = new StepNeuron(100, _organism.NeuralSystem, Guid.NewGuid());
                _ntl = new StepNeuron(100, _organism.NeuralSystem, Guid.NewGuid());
                _ntr = new StepNeuron(100, _organism.NeuralSystem, Guid.NewGuid());
                _nCyclops = new StepNeuron(100, _organism.NeuralSystem, Guid.NewGuid());
                #region cyclops

                #endregion

                #region cyclop turn left

                _synapseOutputCyclops = CreateBasicSynapseSetup(_organism);
                _synapseOutputCyclops.AddSynapticConnectionSource(_nCyclops);
                _synapseOutputCyclops.AddSynapticConnectionTarget(_ntl);
                _synapseOutputCyclops.InitInputWeight = 0.5;
                _ntl.InputSynapse.Add(_synapseOutputCyclops);

                _synapseInputCyclopsVetoForward = CreateBasicSynapseSetup(_organism);
                _synapseInputCyclopsVetoForward.AddSynapticConnectionSource(_nCyclops);
                _synapseInputCyclopsVetoForward.AddSynapticConnectionTarget(_nf);
                _synapseInputCyclopsVetoForward.InitInputWeight = -0.5;

                _synapseInputCyclops = CreateBasicSynapseSetup(_organism);
                _synapseInputCyclops.AddSynapticConnectionSource(_body.GetSynapseIO(Globals.CYCLOPS));
                _synapseInputCyclops.AddSynapticConnectionTarget(_nCyclops);
                _nCyclops.InputSynapse.Add(_synapseInputCyclops);
                _nCyclops.OutputSynapse.Add(_synapseOutputCyclops);
                _nCyclops.OutputSynapse.Add(_synapseInputCyclopsVetoForward);
                _geneCyclops.Cell = _nCyclops;
                #endregion

                #region Foward
                // bumper synapse which vetos the foward motion
                _synapseInputFrontLeftBumperVetoForward = CreateBasicSynapseSetup(_organism);
                _synapseInputFrontLeftBumperVetoForward.AddSynapticConnectionSource(_body.GetSynapseIO(Globals.IROBOTLEFTFRONTBUMPER));
                _synapseInputFrontLeftBumperVetoForward.AddSynapticConnectionTarget(_nf);
                _synapseInputFrontLeftBumperVetoForward.InitInputWeight = -1;

                _synapseInputFrontRightBumperVetoFoward = CreateBasicSynapseSetup(_organism);
                _synapseInputFrontRightBumperVetoFoward.AddSynapticConnectionSource(_body.GetSynapseIO(Globals.IROBOTRIGHTFRONTBUMPER));
                _synapseInputFrontRightBumperVetoFoward.AddSynapticConnectionTarget(_nf);
                _synapseInputFrontRightBumperVetoFoward.InitInputWeight = -1;

                _synapseInputEnergyFoward = CreateBasicSynapseSetup(_organism);
                _synapseInputEnergyFoward.AddSynapticConnectionSource(_body.GetSynapseIO(Globals.ENERGYCHANGE));
                _synapseInputEnergyFoward.AddSynapticConnectionTarget(_nf);
                _synapseInputEnergyFoward.InitInputWeight = -100;

                _nf.InputSynapse.Add(_synapseInputEnergyFoward);
                _nf.InputSynapse.Add(_synapseInputFrontLeftBumperVetoForward);
                _nf.InputSynapse.Add(_synapseInputFrontRightBumperVetoFoward);
                _nf.InputSynapse.Add(_synapseInputCyclopsVetoForward);

                _synapseOutputForward = CreateBasicSynapseSetup(_organism);
                _synapseOutputForward.AddSynapticConnectionTarget(_body.GetSynapseIO(Globals.IROBOTFORWARD));
                _synapseOutputForward.AddSynapticConnectionSource(_nf);
                _nf.OutputSynapse.Add(_synapseOutputForward);
                _geneNeuralForward.Cell = _nf;
                #endregion

                #region Turn Left
                _synapseInputTurnLeft = CreateBasicSynapseSetup(_organism);
                _synapseInputTurnLeft.AddSynapticConnectionSource(_body.GetSynapseIO(Globals.IROBOTRIGHTFRONTBUMPER));
                _synapseInputTurnLeft.AddSynapticConnectionTarget(_ntl);
                _ntl.InputSynapse.Add(_synapseInputTurnLeft);
                _synapseOutputTurnLeft = CreateBasicSynapseSetup(_organism);
                _synapseOutputTurnLeft.AddSynapticConnectionTarget(_body.GetSynapseIO(Globals.IROBOTTURNLEFT));
                _synapseOutputTurnLeft.AddSynapticConnectionSource(_ntl);
                _ntl.OutputSynapse.Add(_synapseOutputTurnLeft);
                _geneNeuralTurnLeft.Cell = _ntl;
                #endregion

                #region Turn Right
                _synapseInputTurnRight = CreateBasicSynapseSetup(_organism);
                _synapseInputTurnRight.AddSynapticConnectionSource(_body.GetSynapseIO(Globals.IROBOTLEFTFRONTBUMPER));
                _synapseInputTurnRight.AddSynapticConnectionTarget(_ntr);
                _ntr.InputSynapse.Add(_synapseInputTurnRight);
                _synapseOutputTurnRight = CreateBasicSynapseSetup(_organism);
                _synapseOutputTurnRight.AddSynapticConnectionTarget(_body.GetSynapseIO(Globals.IROBOTTURNRIGHT));
                _synapseOutputTurnRight.AddSynapticConnectionSource(_ntr);
                _ntr.OutputSynapse.Add(_synapseOutputTurnRight);
                _geneNeuralTurnRight.Cell = _ntr;
                #endregion
                #endregion
            }

            // set alive organisms
            hostEnvironment.Organisms.AliveAdd(_organism);
        }

        private static ISynapse CreateBasicSynapseSetup(IOrganism organism)
        {
            ISynapse synapse = new SimpleSynapse(Guid.NewGuid(), organism.NeuralSystem);
            synapse.LearningRule = new UseItOrLoseIt(0.001, 0.0001, synapse, Guid.NewGuid());
            return synapse;
        }

        private static void CreateGeneWithHBOffsetOf1FromParent(IGeneCollection genes, GeneCellular gene, GeneCellular geneCreate, IRequirementCollection requirements, IOrganism organism)
        {
            genes.Add(gene);
            gene.Organism = organism;
            gene.Requirements = requirements;
            gene.ActionType = ActionType.AddCell;
            requirements.Add(new HBOffsetTimeCell(organism, 1, geneCreate.Cell, ComparisonType.Equal, Guid.NewGuid()));
        }

        private static void CreateiRobotAsexualReproduction(IOrganism organism, IGeneCollection genesReproductive, GeneReproductionAsexual geneASexual, IRequirementCollection requirementsASexual, IPhysical body, Int64 irobotReproductiveEnergy, float irobotMutationRate)
        {
            // add a ASexual gene
            genesReproductive.Add(geneASexual);
            // tell the gene who the Organism is
            geneASexual.Organism = organism;
            // set the mutation rate
            geneASexual.MutationRate = irobotMutationRate;
            // set the requirements list
            geneASexual.Requirements = requirementsASexual;
            // add the requirement to start reproducing
            requirementsASexual.Add(new HBOffsetTimeCell(organism, 5, body, ComparisonType.GreaterThan, Guid.NewGuid()));
            requirementsASexual.Add(new IOSynapseValue(Globals.ENERGY, irobotReproductiveEnergy, organism, ComparisonType.GreaterThan, Guid.NewGuid()));
        }

        private static IRequirement AddGeneToCreateOrganism(IOrganism organism, IGeneCollection genesCellular, GeneCellular geneCreate, IRequirementCollection requirementsCreate, IPhysical body)
        {
            // add a gene to create the Organism
            genesCellular.Add(geneCreate);
            // tell the gene who the Organism is
            geneCreate.Organism = organism;
            // add a requirement colleciton to that gene
            geneCreate.Requirements = requirementsCreate;
            // create a body to start after the first heart beat
            IRequirement requirementBodyCreate = new HBOffsetTime(organism, 1, ComparisonType.Equal, Guid.NewGuid());
            // add a body as this is the first step
            requirementsCreate.Add(requirementBodyCreate);
            // set the action type as adding
            geneCreate.ActionType = ActionType.AddCell;
            // set the gene's cell
            geneCreate.Cell = body;
            return requirementBodyCreate;
        }

        /// <summary>
        /// Saves the organisms back to the database
        /// </summary>
        /// <param name="environment"></param>
        /// <param name="hostEnvironment"></param>
        /// <param name="maxSaveCount">ignored</param>
        /// <returns></returns>
        public bool SaveHostingEnvironment(string environment, IHostingEnvironment hostEnvironment, int maxSaveCount)
        {
            _simEnv.TraceLogger.LogDebug(string.Format("Total Organisms {0}", hostEnvironment.Organisms.Alive.Count + hostEnvironment.Organisms.Dead.Count + hostEnvironment.Organisms.Inactive.Count));
            _simEnv.TraceLogger.LogDebug(string.Format("Alive Organisms {0}", hostEnvironment.Organisms.Alive.Count));
            _simEnv.TraceLogger.LogDebug(string.Format("Dead Organisms {0}", hostEnvironment.Organisms.Dead.Count));
            _simEnv.TraceLogger.LogDebug(string.Format("Inactive Organisms {0}", hostEnvironment.Organisms.Inactive.Count));
            _simEnv.TraceLogger.LogDebug("SaveOrganisms, The method or operation is not implemented.");

            // test, dump to xml
            var xmlTest = new XMLDAL();
            xmlTest.SaveHostingEnvironment(environment, hostEnvironment, 0);

            return true;
        }

    }


}


